//
//  MD2Entity.m
//  CompGraf2
//
//  Created by Juan Niosi on 06/09/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "MD2Model.h"

#define NUMVERTEXNORMALS 162
#define WORLD_SCALE 0.05f

typedef float Normal[3];
static Normal anorms_table[ NUMVERTEXNORMALS ] = {
#include "anorms.h"
};

@implementation MD2Model

@synthesize modelName, bounds;  

typedef struct
{
    u_short vertices[3];
    u_short uvs[3];
} Triangle;


- (id)init
{
    self = [super init];
    if (self) {
        modelName = @"";
        vertCount = 0;
    }
    
    return self;
}

- (id)init:(NSString *)filename
{
    self = [super init];
    if (self) 
    {
        textureIndex = 0;
        textures = [[NSMutableArray alloc] init];
        modelName = filename;
        NSLog(@"Loading model %@", modelName);
        NSString *fullPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"md2"];
        
        //Open file
        FILE *file = fopen([fullPath cStringUsingEncoding:1], "rb");
        
        if (file == nil)
        {
            NSLog(@"Failed to load file %@",filename);
            return self;
        }
        
        //load header data
        int *header = malloc(sizeof(GLint)*17);
        fseek(file, 0, SEEK_SET);
        fread(header, sizeof(GLint), 17, file);

        if (header[0] != 844121161 || header[1] != 8)
        {
            NSLog(@"Invalid file identifier or version");
            return NULL;
        }
        
        int skinwitdh = header[2];
        int skinheight = header[3];
//        int frame_size = header[4];
        int num_skins = header[5];
        int num_verts = header[6];
        int num_uvs = header[7];
        int num_tris = header[8];
        int num_frames = header[10];
        int ofs_skins = header[11];
        int ofs_uvs = header[12];
        int ofs_tris = header[13];
        int ofs_frames = header[14];
        
        
        vertCount = num_tris*3;
        
        //load skins
        int skinOffset = ofs_skins;
        for (int skinIdx = 0; skinIdx < num_skins; skinIdx++)
        {
            fseek(file, skinOffset, SEEK_SET);
            
            //Calcular bien
            skinOffset += skinwitdh*skinheight;
        }
        
        //load uvs
        GLshort *shortUVs = malloc(sizeof(GLshort)*num_uvs*2);
        fseek(file, ofs_uvs, SEEK_SET);
        fread(shortUVs, sizeof(GLshort), num_uvs*2, file);
        
        GLfloat *tempUvs = malloc(sizeof(GLfloat)*num_uvs*2);
        for (int i = 0; i < num_uvs; i++)
        {
            int idx = i*2;
            tempUvs[idx] = (GLfloat)shortUVs[idx] / skinwitdh;
            tempUvs[idx+1] = (GLfloat)shortUVs[idx+1] / skinheight;
        }
        
        bounds = [[Bounds alloc] init];
        NSMutableDictionary *tempAnims = [[NSMutableDictionary alloc] init];
        NSMutableArray *tempFrames = [[NSMutableArray alloc] init];
        NSString* tempAnimName = @"";
        //load triangles
        Triangle *trianglesData = malloc(sizeof(Triangle)*num_tris);
        fseek(file, ofs_tris, SEEK_SET);
        fread(trianglesData, sizeof(Triangle), num_tris, file);

        //order UVS        
        uvs = malloc(sizeof(GLfloat)*6*num_tris);
        int uvOffset = 0;
        for (int i = 0; i < num_tris; i++)
        {
            int uvIdx = trianglesData[i].uvs[0]*2;
            
            uvs[uvOffset] = tempUvs[uvIdx];
            uvOffset++;
            
            uvs[uvOffset] = tempUvs[uvIdx+1];
            uvOffset++;
            
            uvIdx = trianglesData[i].uvs[1]*2;
            
            uvs[uvOffset] = tempUvs[uvIdx];
            uvOffset++;
            
            uvs[uvOffset] = tempUvs[uvIdx+1];
            uvOffset++;
            
            uvIdx = trianglesData[i].uvs[2]*2;
            
            uvs[uvOffset] = tempUvs[uvIdx];
            uvOffset++;
            
            uvs[uvOffset] = tempUvs[uvIdx+1];
            uvOffset++;
        }
        
        //load frames
        fseek(file, ofs_frames, SEEK_SET);
        for (int frameIdx = 0; frameIdx < num_frames; frameIdx++)
        {   
            GLfloat scale[3];
            fread(scale, sizeof(GLfloat), 3, file);
            
            GLfloat translate[3];
            fread(translate, sizeof(GLfloat), 3, file);
            
            char name[16];
            fread(name, sizeof(char), 16, file);
            
            int lastLetter = 16;
            int val = 0;
            while (lastLetter >= 0 && val < 10)
            {
                lastLetter--;
                val = (int)(name[lastLetter] - '0');
            }
            
            NSString *frameName = [[NSString alloc] initWithBytes:name length:sizeof(char)*(lastLetter+1) encoding:NSASCIIStringEncoding];
            //NSLog(@"frame %s", name);
            if (![frameName isEqualToString:tempAnimName])
            {
                if (frameIdx > 0)
                {
                    NSLog(@"  %@ %i",tempAnimName, [tempFrames count]);
                    MD2Anim *anim = [[MD2Anim alloc] initWithName:tempAnimName withFrames:[[NSArray alloc] initWithArray:tempFrames]];
                    [tempAnims  setObject:anim forKey:tempAnimName];
                    
                    tempFrames = [[NSMutableArray alloc] init];
                }
                tempAnimName = frameName;
            }
            
            u_char *vertData = malloc(sizeof(u_char)*num_verts*4);
            fread(vertData, sizeof(u_char), num_verts*4, file);

            GLfloat *verts = malloc(sizeof(GLfloat)*num_verts*3);
            
            GLfloat *norms = malloc(sizeof(GLfloat)*num_verts*3);
            
            u_int vertOffset = 0;
            for (u_int vIdx = 0; vIdx < num_verts; vIdx++)
            {
                u_int dataIdx = vIdx*4;
                u_char normalIdx = vertData[dataIdx+3];
                
                float x = ((scale[0] * vertData[dataIdx]) + translate[0])  * WORLD_SCALE;
                verts[vertOffset] = x;
                norms[vertOffset] = anorms_table[normalIdx][0];
                vertOffset++;
                if (x < bounds.xMin)
                    bounds.xMin = x;
                else if (x > bounds.xMax)
                    bounds.xMax = x;
                
                float z = ((scale[1] * vertData[dataIdx+1]) + translate[1])  * WORLD_SCALE;
                verts[vertOffset] = z;
                norms[vertOffset] = anorms_table[normalIdx][1];
                vertOffset++;
                if (z < bounds.zMin)
                    bounds.zMin = z;
                else if (z > bounds.zMax)
                    bounds.zMax = z;
                
                
                float y = ((scale[2] * vertData[dataIdx+2]) + translate[2])  * WORLD_SCALE;
                verts[vertOffset] = y;
                norms[vertOffset] = anorms_table[normalIdx][2];
                vertOffset++;
                if (y < bounds.yMin)
                    bounds.yMin = y;
                else if (y > bounds.yMax)
                    bounds.yMax = y;
            }
            
            GLfloat *orderedVerts = malloc(sizeof(GLfloat)*num_tris*9);
            GLfloat *orderedNormals = malloc(sizeof(GLfloat)*num_tris*9);
            int vIndex = 0;
            for (u_int triIdx = 0; triIdx < num_tris; triIdx++)
            {
                Triangle tri = trianglesData[triIdx];
                
                for (int triv = 0; triv < 3; triv++)
                {
                    u_short vOffset = tri.vertices[triv]*3;
                    
                    orderedVerts[vIndex] = verts[vOffset];
                    orderedNormals[vIndex] = norms[vOffset];
                    vIndex++;
                    
                    orderedVerts[vIndex] = verts[vOffset+2];
                    orderedNormals[vIndex] = norms[vOffset+2];
                    vIndex++;
                    
                    orderedVerts[vIndex] = verts[vOffset+1];
                    orderedNormals[vIndex] = norms[vOffset+1];
                    vIndex++;
                }
            }
            MD2AnimFrame *animFrame = [[MD2AnimFrame alloc] initWithName:name withVertices:orderedVerts withNormals:orderedNormals];
            [tempFrames addObject:animFrame];
        }
        
        anims = [[NSDictionary alloc] initWithDictionary:tempAnims];
        fclose(file);
    }
    return self;
}

- (MD2AnimFrame *)getInterpolatedFrame:(MD2Anim *)anim:(float)idx
{
    GLfloat *resultVerts;
    GLfloat *resultNormals;
    int prevIdx = (int)floorf(idx) % [anim countFrames];
    int nextIdx = (prevIdx+1) % [anim countFrames];
        
    MD2AnimFrame *prevFrame = [anim getFrame:prevIdx];
    if (nextIdx >= [anim countFrames])
    {
        nextIdx -= [anim countFrames];
    }
    
    resultVerts = malloc(sizeof(GLfloat)*vertCount*3);
    resultNormals = malloc(sizeof(GLfloat)*vertCount*3);
    MD2AnimFrame *nextFrame = (MD2AnimFrame *)[anim getFrame:nextIdx];
    
    float delta = idx - prevIdx;
    for (int i = 0; i < vertCount*3; i++)
    {
        resultVerts[i] = prevFrame.vertices[i] + (nextFrame.vertices[i] - prevFrame.vertices[i])*delta;
        resultNormals[i] = prevFrame.normals[i] + (nextFrame.normals[i] - prevFrame.normals[i])*delta;
    }
    
    
    return [[MD2AnimFrame alloc] initWithName:prevFrame.name withVertices:resultVerts withNormals:resultNormals];
}

- (BOOL)draw:(Engine *)engine withAnim:(NSString *)animName atTime:(float)frameIdx looping:(BOOL)loop;
{
    BOOL end = false;
    MD2Anim *anim = [anims objectForKey:animName];
    if (anim == NULL)
    {
        NSLog(@"anim %@ in md2Model %@ not found",animName,modelName);
        return false;
    }
    
    if (floor(frameIdx) >= [anim countFrames])
    {
        int nFrames = [anim countFrames];
        if (loop)
        {
            while (frameIdx > nFrames)
                frameIdx -= nFrames;
        }
        else
        {
            frameIdx = [anim countFrames] -1;
            end = true;
        }
    }
    MD2AnimFrame *frame = [self getInterpolatedFrame:anim:frameIdx];
    
    if ([textures count] == 0)
    {
        [engine drawGeometry:frame.vertices vertCount:vertCount];
    }
    else
    {
        [engine drawGeometry:frame.vertices vertCount:vertCount withTexture:[textures objectAtIndex:textureIndex] setUVs:uvs setNormals:frame.normals];
    }
    
    [frame kill];
    
    return !end;
}

- (void)addTexture:(Texture *)tex
{
    if (tex == nil)
        return;
    [textures addObject:tex];
}

- (BOOL)hasAnim:(NSString*)animName
{
    return [anims objectForKey:animName] != NULL;
}

- (void)dealloc
{
    [modelName release];
    [anims release];
    [bounds release];
    free(uvs);
    [textures release];
    [super release];
}
@end
